"use client" import { useState, useEffect, useRef, useCallback } from "react" import Link from "next/link" import { useParams, useSearchParams, useRouter } from "next/navigation" import { getRun, getRunReport, stopRun, startRun, type RunDetail } from "@/lib/api" import { formatDate, getStatusColor, cn } from "@/lib/utils" import { PhaseProgress } from "@/components/phase-progress" import { QuestionList } from "@/components/question-list" import { StatsGrid, AccuracyByType, LatencyTable, RetrievalMetrics, } from "@/components/benchmark-results" type Tab = "overview" | "results" const POLL_INTERVAL = 2000 // 2 seconds export default function RunDetailPage() { const params = useParams() const searchParams = useSearchParams() const router = useRouter() const runId = decodeURIComponent(params.runId as string) const pollIntervalRef = useRef(null) // Get tab from URL or default to "overview" const tabFromUrl = searchParams.get("tab") as Tab | null const initialTab: Tab = tabFromUrl && ["overview", "results"].includes(tabFromUrl) ? tabFromUrl : "overview" const [run, setRun] = useState(null) const [report, setReport] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [activeTab, setActiveTab] = useState(initialTab) const [terminating, setTerminating] = useState(false) // Check if run is in progress (include stopping to keep polling until fully stopped) const isInitializing = run?.status === "initializing" const isRunning = run?.status === "running" || run?.status === "pending" || run?.status === "stopping" || isInitializing const isStopping = run?.status === "stopping" const isFailed = run?.status === "failed" const isPartial = run?.status === "partial" const canContinue = isFailed || isPartial const [continuing, setContinuing] = useState(false) async function handleContinue() { if (continuing || !run) return setContinuing(true) try { await startRun({ provider: run.provider, benchmark: run.benchmark, runId: run.runId, judgeModel: run.judge, answeringModel: run.answeringModel, }) await refreshData() } catch (e) { console.error("Failed to continue:", e) } finally { setContinuing(false) } } async function handleTerminate() { if (terminating) return setTerminating(true) try { await stopRun(runId) await refreshData() } catch (e) { console.error("Failed to terminate:", e) } finally { setTerminating(false) } } // Update URL when tab changes const handleTabChange = (tab: Tab) => { setActiveTab(tab) const newUrl = tab === "overview" ? `/runs/${encodeURIComponent(runId)}` : `/runs/${encodeURIComponent(runId)}?tab=${tab}` router.replace(newUrl, { scroll: false }) } // Silent refresh (no loading state) const refreshData = useCallback(async () => { try { const [runData, reportData] = await Promise.all([ getRun(runId), getRunReport(runId).catch(() => null), ]) setRun(runData) setReport(reportData) setError(null) } catch (e) { // Silent fail on poll } }, [runId]) // Initial load useEffect(() => { loadData() }, [runId]) // Polling when run is in progress useEffect(() => { if (isRunning) { pollIntervalRef.current = setInterval(refreshData, POLL_INTERVAL) } else { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current) pollIntervalRef.current = null } } return () => { if (pollIntervalRef.current) { clearInterval(pollIntervalRef.current) } } }, [isRunning, refreshData]) async function loadData() { try { setLoading(true) const [runData, reportData] = await Promise.all([ getRun(runId), getRunReport(runId).catch(() => null), ]) setRun(runData) setReport(reportData) setError(null) } catch (e) { setError(e instanceof Error ? e.message : "Failed to load run") } finally { setLoading(false) } } if (loading) { return (
) } if (error || !run) { return (

{error || "Run not found"}

Back to runs
) } // Show initializing state while benchmark is loading/downloading if (isInitializing) { return (
{/* Breadcrumb */}
Runs / {runId}
{/* Header */}

{runId} initializing

Provider:{" "} {run.provider} Benchmark:{" "} {run.benchmark}
{/* Loading State */}

Loading benchmark dataset...

This may take a moment for first-time downloads

) } const allQuestions = Object.values(run.questions) // Only count questions that have been evaluated const evaluatedQuestions = allQuestions.filter((q) => q.phases.evaluate.status === "completed") const failedQuestions = evaluatedQuestions.filter((q) => q.phases.evaluate.label === "incorrect") const accuracy = report?.summary?.accuracy ?? (evaluatedQuestions.length > 0 ? (evaluatedQuestions.filter((q) => q.phases.evaluate.score === 1).length / evaluatedQuestions.length) * 100 : 0) // Find error from failed phases const runError = (() => { for (const q of allQuestions) { const phases = q.phases as Record for (const phase of ["ingest", "indexing", "search", "answer", "evaluate"]) { if (phases[phase]?.status === "failed" && phases[phase]?.error) { return phases[phase].error } } } return null })() return (
{/* Breadcrumb */}
Runs / {runId}
{/* Header */}

{runId} {run.status} {(isRunning || isStopping) && ( )} {canContinue && ( )}

Provider:{" "} {run.provider} Benchmark:{" "} {run.benchmark} Judge: {run.judge} Created: {formatDate(run.createdAt)}
{/* Error Display */} {runError && (

Error {runError}

)} {/* Phase Progress */} {/* Tabs */}
{(["overview", "results"] as Tab[]).map((tab) => ( ))}
{/* Tab Content */} {activeTab === "overview" && (
)} {activeTab === "results" && }
) }